/*!
 * Uploader - Uploader library implements html5 file upload and provides multiple simultaneous, stable, fault tolerant and resumable uploads
 * @version v0.5.0
 * @author dolymood <dolymood@gmail.com>
 * @link https://github.com/simple-uploader/Uploader
 * @license MIT
 */
!(function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Uploader=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
    var utils = _dereq_('./utils')
    
    function Chunk (uploader, file, offset) {
      utils.defineNonEnumerable(this, 'uploader', uploader)
      utils.defineNonEnumerable(this, 'file', file)
      utils.defineNonEnumerable(this, 'bytes', null)
      this.offset = offset
      this.tested = false
      this.retries = 0
      this.pendingRetry = false
      this.preprocessState = 0
      this.readState = 0
      this.loaded = 0
      this.total = 0
      this.chunkSize = this.uploader.opts.chunkSize
      this.startByte = this.offset * this.chunkSize
      this.endByte = this.computeEndByte()
      this.xhr = null
    }
    
    var STATUS = Chunk.STATUS = {
      PENDING: 'pending',
      UPLOADING: 'uploading',
      READING: 'reading',
      SUCCESS: 'success',
      ERROR: 'error',
      COMPLETE: 'complete',
      PROGRESS: 'progress',
      RETRY: 'retry'
    }
    
    utils.extend(Chunk.prototype, {
    
      _event: function (evt, args) {
        args = utils.toArray(arguments)
        args.unshift(this)
        this.file._chunkEvent.apply(this.file, args)
      },
    
      computeEndByte: function () {
        var endByte = Math.min(this.file.size, (this.offset + 1) * this.chunkSize)
        if (this.file.size - endByte < this.chunkSize && !this.uploader.opts.forceChunkSize) {
          // The last chunk will be bigger than the chunk size,
          // but less than 2 * this.chunkSize
          endByte = this.file.size
        }
        return endByte
      },
    
      getParams: function () {
        return {
          chunkNumber: this.offset + 1,
          chunkSize: this.uploader.opts.chunkSize,
          currentChunkSize: this.endByte - this.startByte,
          totalSize: this.file.size,
          identifier: this.file.uniqueIdentifier,
          filename: this.file.name,
          relativePath: this.file.relativePath,
          totalChunks: this.file.chunks.length
        }
      },
    
      getTarget: function (target, params) {
        if (!params.length) {
          return target
        }
        if (target.indexOf('?') < 0) {
          target += '?'
        } else {
          target += '&'
        }
        return target + params.join('&')
      },
    
      test: function () {
        this.xhr = new XMLHttpRequest()
        this.xhr.addEventListener('load', testHandler, false)
        this.xhr.addEventListener('error', testHandler, false)
        var testMethod = utils.evalOpts(this.uploader.opts.testMethod, this.file, this)
        var data = this.prepareXhrRequest(testMethod, true)
        this.xhr.send(data)
    
        var $ = this
        function testHandler (event) {
          var status = $.status(true)
          if (status === STATUS.ERROR) {
            $._event(status, $.message())
            $.uploader.uploadNextChunk()
          } else if (status === STATUS.SUCCESS) {
            $._event(status, $.message())
            $.tested = true
          } else if (!$.file.paused) {
            // Error might be caused by file pause method
            // Chunks does not exist on the server side
            $.tested = true
            $.send()
          }
        }
      },
    
      preprocessFinished: function () {
        // Compute the endByte after the preprocess function to allow an
        // implementer of preprocess to set the fileObj size
        this.endByte = this.computeEndByte()
        this.preprocessState = 2
        this.send()
      },
    
      readFinished: function (bytes) {
        this.readState = 2
        this.bytes = bytes
        this.send()
      },
    
      send: function () {
        var preprocess = this.uploader.opts.preprocess
        var read = this.uploader.opts.readFileFn
        if (utils.isFunction(preprocess)) {
          switch (this.preprocessState) {
            case 0:
              this.preprocessState = 1
              preprocess(this)
              return
            case 1:
              return
          }
        }
        switch (this.readState) {
          case 0:
            this.readState = 1
            read(this.file, this.file.fileType, this.startByte, this.endByte, this)
            return
          case 1:
            return
        }
        if (this.uploader.opts.testChunks && !this.tested) {
          this.test()
          return
        }
    
        this.loaded = 0
        this.total = 0
        this.pendingRetry = false
    
        // Set up request and listen for event
        this.xhr = new XMLHttpRequest()
        this.xhr.upload.addEventListener('progress', progressHandler, false)
        this.xhr.addEventListener('load', doneHandler, false)
        this.xhr.addEventListener('error', doneHandler, false)
    
        var uploadMethod = utils.evalOpts(this.uploader.opts.uploadMethod, this.file, this)
        var data = this.prepareXhrRequest(uploadMethod, false, this.uploader.opts.method, this.bytes)
        this.xhr.send(data)
    
        var $ = this
        function progressHandler (event) {
          if (event.lengthComputable) {
            $.loaded = event.loaded
            $.total = event.total
          }
          $._event(STATUS.PROGRESS, event)
        }
    
        function doneHandler (event) {
          var msg = $.message()
          $.processingResponse = true
          $.uploader.opts.processResponse(msg, function (err, res) {
            $.processingResponse = false
            if (!$.xhr) {
              return
            }
            $.processedState = {
              err: err,
              res: res
            }
            var status = $.status()
            if (status === STATUS.SUCCESS || status === STATUS.ERROR) {
              delete this.data
              $._event(status, res)
              status === STATUS.ERROR && $.uploader.uploadNextChunk()
            } else {
              $._event(STATUS.RETRY, res)
              $.pendingRetry = true
              $.abort()
              $.retries++
              var retryInterval = $.uploader.opts.chunkRetryInterval
              if (retryInterval !== null) {
                setTimeout(function () {
                  $.send()
                }, retryInterval)
              } else {
                $.send()
              }
            }
          })
        }
      },
    
      abort: function () {
        var xhr = this.xhr
        this.xhr = null
        this.processingResponse = false
        this.processedState = null
        if (xhr) {
          xhr.abort()
        }
      },
    
      status: function (isTest) {
        if (this.readState === 1) {
          return STATUS.READING
        } else if (this.pendingRetry || this.preprocessState === 1) {
          // if pending retry then that's effectively the same as actively uploading,
          // there might just be a slight delay before the retry starts
          return STATUS.UPLOADING
        } else if (!this.xhr) {
          return STATUS.PENDING
        } else if (this.xhr.readyState < 4 || this.processingResponse) {
          // Status is really 'OPENED', 'HEADERS_RECEIVED'
          // or 'LOADING' - meaning that stuff is happening
          return STATUS.UPLOADING
        } else {
          var _status
          if (this.uploader.opts.successStatuses.indexOf(this.xhr.status) > -1) {
            // HTTP 200, perfect
            // HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed.
            _status = STATUS.SUCCESS
          } else if (this.uploader.opts.permanentErrors.indexOf(this.xhr.status) > -1 ||
              !isTest && this.retries >= this.uploader.opts.maxChunkRetries) {
            // HTTP 415/500/501, permanent error
            _status = STATUS.ERROR
          } else {
            // this should never happen, but we'll reset and queue a retry
            // a likely case for this would be 503 service unavailable
            this.abort()
            _status = STATUS.PENDING
          }
          var processedState = this.processedState
          if (processedState && processedState.err) {
            _status = STATUS.ERROR
          }
          return _status
        }
      },
    
      message: function () {
        return this.xhr ? this.xhr.responseText : ''
      },
    
      progress: function () {
        if (this.pendingRetry) {
          return 0
        }
        var s = this.status()
        if (s === STATUS.SUCCESS || s === STATUS.ERROR) {
          return 1
        } else if (s === STATUS.PENDING) {
          return 0
        } else {
          return this.total > 0 ? this.loaded / this.total : 0
        }
      },
    
      sizeUploaded: function () {
        var size = this.endByte - this.startByte
        // can't return only chunk.loaded value, because it is bigger than chunk size
        if (this.status() !== STATUS.SUCCESS) {
          size = this.progress() * size
        }
        return size
      },
    
      prepareXhrRequest: function (method, isTest, paramsMethod, blob) {
        // Add data from the query options
        var query = utils.evalOpts(this.uploader.opts.query, this.file, this, isTest)
        query = utils.extend(this.getParams(), query)
    
        // processParams
        query = this.uploader.opts.processParams(query)
    
        var target = utils.evalOpts(this.uploader.opts.target, this.file, this, isTest)
        var data = null
        if (method === 'GET' || paramsMethod === 'octet') {
          // Add data from the query options
          var params = []
          utils.each(query, function (v, k) {
            params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='))
          })
          target = this.getTarget(target, params)
          data = blob || null
        } else {
          // Add data from the query options
          data = new FormData()
          utils.each(query, function (v, k) {
            data.append(k, v)
          })
          data.append(this.uploader.opts.fileParameterName, blob, this.file.name)
        }
    
        this.xhr.open(method, target, true)
        this.xhr.withCredentials = this.uploader.opts.withCredentials
    
        // Add data from header options
        utils.each(utils.evalOpts(this.uploader.opts.headers, this.file, this, isTest), function (v, k) {
          this.xhr.setRequestHeader(k, v)
        }, this)
    
        return data
      }
    
    })
    
    module.exports = Chunk
    
    },{"./utils":5}],2:[function(_dereq_,module,exports){
    var each = _dereq_('./utils').each
    
    var event = {
    
      _eventData: null,
    
      on: function (name, func) {
        if (!this._eventData) this._eventData = {}
        if (!this._eventData[name]) this._eventData[name] = []
        var listened = false
        each(this._eventData[name], function (fuc) {
          if (fuc === func) {
            listened = true
            return false
          }
        })
        if (!listened) {
          this._eventData[name].push(func)
        }
      },
    
      off: function (name, func) {
        if (!this._eventData) this._eventData = {}
        if (!this._eventData[name] || !this._eventData[name].length) return
        if (func) {
          each(this._eventData[name], function (fuc, i) {
            if (fuc === func) {
              this._eventData[name].splice(i, 1)
              return false
            }
          }, this)
        } else {
          this._eventData[name] = []
        }
      },
    
      trigger: function (name) {
        if (!this._eventData) this._eventData = {}
        if (!this._eventData[name]) return true
        var args = this._eventData[name].slice.call(arguments, 1)
        var preventDefault = false
        each(this._eventData[name], function (fuc) {
          preventDefault = fuc.apply(this, args) === false || preventDefault
        }, this)
        return !preventDefault
      }
    }
    
    module.exports = event
    
    },{"./utils":5}],3:[function(_dereq_,module,exports){
    var utils = _dereq_('./utils')
    var event = _dereq_('./event')
    var File = _dereq_('./file')
    var Chunk = _dereq_('./chunk')
    
    var version = '0.5.0'
    
    var isServer = typeof window === 'undefined'
    
    // ie10+
    var ie10plus = isServer ? false : window.navigator.msPointerEnabled
    var support = (function () {
      if (isServer) {
        return false
      }
      var sliceName = 'slice'
      var _support = utils.isDefined(window.File) && utils.isDefined(window.Blob) &&
                    utils.isDefined(window.FileList)
      var bproto = null
      if (_support) {
        bproto = window.Blob.prototype
        utils.each(['slice', 'webkitSlice', 'mozSlice'], function (n) {
          if (bproto[n]) {
            sliceName = n
            return false
          }
        })
        _support = !!bproto[sliceName]
      }
      if (_support) Uploader.sliceName = sliceName
      bproto = null
      return _support
    })()
    
    var supportDirectory = (function () {
      if (isServer) {
        return false
      }
      var input = window.document.createElement('input')
      input.type = 'file'
      var sd = 'webkitdirectory' in input || 'directory' in input
      input = null
      return sd
    })()
    
    function Uploader (opts) {
      this.support = support
      /* istanbul ignore if */
      if (!this.support) {
        return
      }
      this.supportDirectory = supportDirectory
      utils.defineNonEnumerable(this, 'filePaths', {})
      this.opts = utils.extend({}, Uploader.defaults, opts || {})
    
      this.preventEvent = utils.bind(this._preventEvent, this)
    
      File.call(this, this)
    }
    
    /**
     * Default read function using the webAPI
     *
     * @function webAPIFileRead(fileObj, fileType, startByte, endByte, chunk)
     *
     */
    var webAPIFileRead = function (fileObj, fileType, startByte, endByte, chunk) {
      chunk.readFinished(fileObj.file[Uploader.sliceName](startByte, endByte, fileType))
    }
    
    Uploader.version = version
    
    Uploader.defaults = {
      chunkSize: 1024 * 1024,
      forceChunkSize: false,
      simultaneousUploads: 3,
      singleFile: false,
      fileParameterName: 'file',
      progressCallbacksInterval: 500,
      speedSmoothingFactor: 0.1,
      query: {},
      headers: {},
      withCredentials: false,
      preprocess: null,
      method: 'multipart',
      testMethod: 'GET',
      uploadMethod: 'POST',
      prioritizeFirstAndLastChunk: false,
      allowDuplicateUploads: false,
      target: '/',
      testChunks: true,
      generateUniqueIdentifier: null,
      maxChunkRetries: 0,
      chunkRetryInterval: null,
      permanentErrors: [404, 415, 500, 501],
      successStatuses: [200, 201, 202],
      onDropStopPropagation: false,
      initFileFn: null,
      readFileFn: webAPIFileRead,
      checkChunkUploadedByResponse: null,
      initialPaused: false,
      processResponse: function (response, cb) {
        cb(null, response)
      },
      processParams: function (params) {
        return params
      }
    }
    
    Uploader.utils = utils
    Uploader.event = event
    Uploader.File = File
    Uploader.Chunk = Chunk
    
    // inherit file
    Uploader.prototype = utils.extend({}, File.prototype)
    // inherit event
    utils.extend(Uploader.prototype, event)
    utils.extend(Uploader.prototype, {
    
      constructor: Uploader,
    
      _trigger: function (name) {
        var args = utils.toArray(arguments)
        var preventDefault = !this.trigger.apply(this, arguments)
        if (name !== 'catchAll') {
          args.unshift('catchAll')
          preventDefault = !this.trigger.apply(this, args) || preventDefault
        }
        return !preventDefault
      },
    
      _triggerAsync: function () {
        var args = arguments
        utils.nextTick(function () {
          this._trigger.apply(this, args)
        }, this)
      },
    
      addFiles: function (files, evt) {
        var _files = []
        var oldFileListLen = this.fileList.length
        utils.each(files, function (file) {
          // Uploading empty file IE10/IE11 hangs indefinitely
          // Directories have size `0` and name `.`
          // Ignore already added files if opts.allowDuplicateUploads is set to false
          if ((!ie10plus || ie10plus && file.size > 0) && !(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.'))) {
            var uniqueIdentifier = this.generateUniqueIdentifier(file)
            if (this.opts.allowDuplicateUploads || !this.getFromUniqueIdentifier(uniqueIdentifier)) {
              var _file = new File(this, file, this)
              _file.uniqueIdentifier = uniqueIdentifier
              if (this._trigger('fileAdded', _file, evt)) {
                _files.push(_file)
              } else {
                File.prototype.removeFile.call(this, _file)
              }
            }
          }
        }, this)
        // get new fileList
        var newFileList = this.fileList.slice(oldFileListLen)
        if (this._trigger('filesAdded', _files, newFileList, evt)) {
          utils.each(_files, function (file) {
            if (this.opts.singleFile && this.files.length > 0) {
              this.removeFile(this.files[0])
            }
            this.files.push(file)
          }, this)
          this._trigger('filesSubmitted', _files, newFileList, evt)
        } else {
          utils.each(newFileList, function (file) {
            File.prototype.removeFile.call(this, file)
          }, this)
        }
      },
    
      addFile: function (file, evt) {
        this.addFiles([file], evt)
      },
    
      cancel: function () {
        for (var i = this.fileList.length - 1; i >= 0; i--) {
          this.fileList[i].cancel()
        }
      },
    
      removeFile: function (file) {
        File.prototype.removeFile.call(this, file)
        this._trigger('fileRemoved', file)
      },
    
      generateUniqueIdentifier: function (file) {
        var custom = this.opts.generateUniqueIdentifier
        if (utils.isFunction(custom)) {
          return custom(file)
        }
        /* istanbul ignore next */
        // Some confusion in different versions of Firefox
        var relativePath = file.relativePath || file.webkitRelativePath || file.fileName || file.name
        /* istanbul ignore next */
        return file.size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '')
      },
    
      getFromUniqueIdentifier: function (uniqueIdentifier) {
        var ret = false
        utils.each(this.files, function (file) {
          if (file.uniqueIdentifier === uniqueIdentifier) {
            ret = file
            return false
          }
        })
        return ret
      },
    
      uploadNextChunk: function (preventEvents) {
        var found = false
        var pendingStatus = Chunk.STATUS.PENDING
        var checkChunkUploaded = this.uploader.opts.checkChunkUploadedByResponse
        if (this.opts.prioritizeFirstAndLastChunk) {
          utils.each(this.files, function (file) {
            if (file.paused) {
              return
            }
            if (checkChunkUploaded && !file._firstResponse && file.isUploading()) {
              // waiting for current file's first chunk response
              return
            }
            if (file.chunks.length && file.chunks[0].status() === pendingStatus) {
              file.chunks[0].send()
              found = true
              return false
            }
            if (file.chunks.length > 1 && file.chunks[file.chunks.length - 1].status() === pendingStatus) {
              file.chunks[file.chunks.length - 1].send()
              found = true
              return false
            }
          })
          if (found) {
            return found
          }
        }
    
        // Now, simply look for the next, best thing to upload
        utils.each(this.files, function (file) {
          if (!file.paused) {
            if (checkChunkUploaded && !file._firstResponse && file.isUploading()) {
              // waiting for current file's first chunk response
              return
            }
            utils.each(file.chunks, function (chunk) {
              if (chunk.status() === pendingStatus) {
                chunk.send()
                found = true
                return false
              }
            })
          }
          if (found) {
            return false
          }
        })
        if (found) {
          return true
        }
    
        // The are no more outstanding chunks to upload, check is everything is done
        var outstanding = false
        utils.each(this.files, function (file) {
          if (!file.isComplete()) {
            outstanding = true
            return false
          }
        })
        // should check files now
        // if now files in list
        // should not trigger complete event
        if (!outstanding && !preventEvents && this.files.length) {
          // All chunks have been uploaded, complete
          this._triggerAsync('complete')
        }
        return outstanding
      },
    
      upload: function (preventEvents) {
        // Make sure we don't start too many uploads at once
        var ret = this._shouldUploadNext()
        if (ret === false) {
          return
        }
        !preventEvents && this._trigger('uploadStart')
        var started = false
        for (var num = 1; num <= this.opts.simultaneousUploads - ret; num++) {
          started = this.uploadNextChunk(!preventEvents) || started
          if (!started && preventEvents) {
            // completed
            break
          }
        }
        if (!started && !preventEvents) {
          this._triggerAsync('complete')
        }
      },
    
      /**
       * should upload next chunk
       * @function
       * @returns {Boolean|Number}
       */
      _shouldUploadNext: function () {
        var num = 0
        var should = true
        var simultaneousUploads = this.opts.simultaneousUploads
        var uploadingStatus = Chunk.STATUS.UPLOADING
        utils.each(this.files, function (file) {
          utils.each(file.chunks, function (chunk) {
            if (chunk.status() === uploadingStatus) {
              num++
              if (num >= simultaneousUploads) {
                should = false
                return false
              }
            }
          })
          return should
        })
        // if should is true then return uploading chunks's length
        return should && num
      },
    
      /**
       * Assign a browse action to one or more DOM nodes.
       * @function
       * @param {Element|Array.<Element>} domNodes
       * @param {boolean} isDirectory Pass in true to allow directories to
       * @param {boolean} singleFile prevent multi file upload
       * @param {Object} attributes set custom attributes:
       *  http://www.w3.org/TR/html-markup/input.file.html#input.file-attributes
       *  eg: accept: 'image/*'
       * be selected (Chrome only).
       */
      assignBrowse: function (domNodes, isDirectory, singleFile, attributes) {
        if (typeof domNodes.length === 'undefined') {
          domNodes = [domNodes]
        }
    
        utils.each(domNodes, function (domNode) {
          var input
          if (domNode.tagName === 'INPUT' && domNode.type === 'file') {
            input = domNode
          } else {
            input = document.createElement('input')
            input.setAttribute('type', 'file')
            // display:none - not working in opera 12
            utils.extend(input.style, {
              visibility: 'hidden',
              position: 'absolute',
              width: '1px',
              height: '1px'
            })
            // for opera 12 browser, input must be assigned to a document
            domNode.appendChild(input)
            // https://developer.mozilla.org/en/using_files_from_web_applications)
            // event listener is executed two times
            // first one - original mouse click event
            // second - input.click(), input is inside domNode
            domNode.addEventListener('click', function (e) {
              if (domNode.tagName.toLowerCase() === 'label') {
                return
              }
              input.click()
            }, false)
          }
          if (!this.opts.singleFile && !singleFile) {
            input.setAttribute('multiple', 'multiple')
          }
          if (isDirectory) {
            input.setAttribute('webkitdirectory', 'webkitdirectory')
          }
          attributes && utils.each(attributes, function (value, key) {
            input.setAttribute(key, value)
          })
          // When new files are added, simply append them to the overall list
          var that = this
          input.addEventListener('change', function (e) {
            that._trigger(e.type, e)
            if (e.target.value) {
              that.addFiles(e.target.files, e)
              e.target.value = ''
            }
          }, false)
        }, this)
      },
    
      onDrop: function (evt) {
        this._trigger(evt.type, evt)
        if (this.opts.onDropStopPropagation) {
          evt.stopPropagation()
        }
        evt.preventDefault()
        this._parseDataTransfer(evt.dataTransfer, evt)
      },
    
      _parseDataTransfer: function (dataTransfer, evt) {
        if (dataTransfer.items && dataTransfer.items[0] &&
          dataTransfer.items[0].webkitGetAsEntry) {
          this.webkitReadDataTransfer(dataTransfer, evt)
        } else {
          this.addFiles(dataTransfer.files, evt)
        }
      },
    
      webkitReadDataTransfer: function (dataTransfer, evt) {
        var self = this
        var queue = dataTransfer.items.length
        var files = []
        utils.each(dataTransfer.items, function (item) {
          var entry = item.webkitGetAsEntry()
          if (!entry) {
            decrement()
            return
          }
          if (entry.isFile) {
            // due to a bug in Chrome's File System API impl - #149735
            fileReadSuccess(item.getAsFile(), entry.fullPath)
          } else {
            readDirectory(entry.createReader())
          }
        })
        function readDirectory (reader) {
          reader.readEntries(function (entries) {
            if (entries.length) {
              queue += entries.length
              utils.each(entries, function (entry) {
                if (entry.isFile) {
                  var fullPath = entry.fullPath
                  entry.file(function (file) {
                    fileReadSuccess(file, fullPath)
                  }, readError)
                } else if (entry.isDirectory) {
                  readDirectory(entry.createReader())
                }
              })
              readDirectory(reader)
            } else {
              decrement()
            }
          }, readError)
        }
        function fileReadSuccess (file, fullPath) {
          // relative path should not start with "/"
          file.relativePath = fullPath.substring(1)
          files.push(file)
          decrement()
        }
        function readError (fileError) {
          throw fileError
        }
        function decrement () {
          if (--queue === 0) {
            self.addFiles(files, evt)
          }
        }
      },
    
      _assignHelper: function (domNodes, handles, remove) {
        if (typeof domNodes.length === 'undefined') {
          domNodes = [domNodes]
        }
        var evtMethod = remove ? 'removeEventListener' : 'addEventListener'
        utils.each(domNodes, function (domNode) {
          utils.each(handles, function (handler, name) {
            domNode[evtMethod](name, handler, false)
          }, this)
        }, this)
      },
    
      _preventEvent: function (e) {
        utils.preventEvent(e)
        this._trigger(e.type, e)
      },
    
      /**
       * Assign one or more DOM nodes as a drop target.
       * @function
       * @param {Element|Array.<Element>} domNodes
       */
      assignDrop: function (domNodes) {
        this._onDrop = utils.bind(this.onDrop, this)
        this._assignHelper(domNodes, {
          dragover: this.preventEvent,
          dragenter: this.preventEvent,
          dragleave: this.preventEvent,
          drop: this._onDrop
        })
      },
    
      /**
       * Un-assign drop event from DOM nodes
       * @function
       * @param domNodes
       */
      unAssignDrop: function (domNodes) {
        this._assignHelper(domNodes, {
          dragover: this.preventEvent,
          dragenter: this.preventEvent,
          dragleave: this.preventEvent,
          drop: this._onDrop
        }, true)
        this._onDrop = null
      }
    })
    
    module.exports = Uploader
    
    },{"./chunk":1,"./event":2,"./file":4,"./utils":5}],4:[function(_dereq_,module,exports){
    var utils = _dereq_('./utils')
    var Chunk = _dereq_('./chunk')
    
    function File (uploader, file, parent) {
      utils.defineNonEnumerable(this, 'uploader', uploader)
      this.isRoot = this.isFolder = uploader === this
      utils.defineNonEnumerable(this, 'parent', parent || null)
      utils.defineNonEnumerable(this, 'files', [])
      utils.defineNonEnumerable(this, 'fileList', [])
      utils.defineNonEnumerable(this, 'chunks', [])
      utils.defineNonEnumerable(this, '_errorFiles', [])
      utils.defineNonEnumerable(this, 'file', null)
      this.id = utils.uid()
    
      if (this.isRoot || !file) {
        this.file = null
      } else {
        if (utils.isString(file)) {
          // folder
          this.isFolder = true
          this.file = null
          this.path = file
          if (this.parent.path) {
            file = file.substr(this.parent.path.length)
          }
          this.name = file.charAt(file.length - 1) === '/' ? file.substr(0, file.length - 1) : file
        } else {
          this.file = file
          this.fileType = this.file.type
          this.name = file.fileName || file.name
          this.size = file.size
          this.relativePath = file.relativePath || file.webkitRelativePath || this.name
          this._parseFile()
        }
      }
    
      this.paused = uploader.opts.initialPaused
      this.error = false
      this.allError = false
      this.aborted = false
      this.averageSpeed = 0
      this.currentSpeed = 0
      this._lastProgressCallback = Date.now()
      this._prevUploadedSize = 0
      this._prevProgress = 0
    
      this.bootstrap()
    }
    
    utils.extend(File.prototype, {
    
      _parseFile: function () {
        var ppaths = parsePaths(this.relativePath)
        if (ppaths.length) {
          var filePaths = this.uploader.filePaths
          utils.each(ppaths, function (path, i) {
            var folderFile = filePaths[path]
            if (!folderFile) {
              folderFile = new File(this.uploader, path, this.parent)
              filePaths[path] = folderFile
              this._updateParentFileList(folderFile)
            }
            this.parent = folderFile
            folderFile.files.push(this)
            if (!ppaths[i + 1]) {
              folderFile.fileList.push(this)
            }
          }, this)
        } else {
          this._updateParentFileList()
        }
      },
    
      _updateParentFileList: function (file) {
        if (!file) {
          file = this
        }
        var p = this.parent
        if (p) {
          p.fileList.push(file)
        }
      },
    
      _eachAccess: function (eachFn, fileFn) {
        if (this.isFolder) {
          utils.each(this.files, function (f, i) {
            return eachFn.call(this, f, i)
          }, this)
          return
        }
        fileFn.call(this, this)
      },
    
      bootstrap: function () {
        if (this.isFolder) return
        var opts = this.uploader.opts
        if (utils.isFunction(opts.initFileFn)) {
          opts.initFileFn.call(this, this)
        }
    
        this.abort(true)
        this._resetError()
        // Rebuild stack of chunks from file
        this._prevProgress = 0
        var round = opts.forceChunkSize ? Math.ceil : Math.floor
        var chunks = Math.max(round(this.size / opts.chunkSize), 1)
        for (var offset = 0; offset < chunks; offset++) {
          this.chunks.push(new Chunk(this.uploader, this, offset))
        }
      },
    
      _measureSpeed: function (hard) {
        var averageSpeeds = 0
        var currentSpeeds = 0
        var num = 0
        this._eachAccess(function (file) {
          if (!file.paused && !file.error) {
            num += 1
            averageSpeeds += file.averageSpeed || 0
            currentSpeeds += file.currentSpeed || 0
          }
        }, function () {
          var timeSpan = Date.now() - this._lastProgressCallback
          if (!timeSpan) {
            return
          }
          var smoothingFactor = this.uploader.opts.speedSmoothingFactor
          var uploaded = this.sizeUploaded()
          // Prevent negative upload speed after file upload resume
          this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0)
          this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed
          this._prevUploadedSize = uploaded
        })
        if (this.isFolder) {
          if (num) {
            this.currentSpeed = currentSpeeds / num
            this.averageSpeed = averageSpeeds / num
          } else {
            this.currentSpeed = 0
            this.averageSpeed = 0
          }
        }
        if (this.parent && (hard || this.parent._checkProgress())) {
          this.parent._measureSpeed()
        }
      },
    
      _checkProgress: function (file) {
        return Date.now() - this._lastProgressCallback >= this.uploader.opts.progressCallbacksInterval
      },
    
      _chunkEvent: function (chunk, evt, message) {
        var uploader = this.uploader
        var STATUS = Chunk.STATUS
        var that = this
        var rootFile = this.getRoot()
        var triggerProgress = function (hard) {
          that._measureSpeed(hard)
          uploader._trigger('fileProgress', rootFile, that, chunk)
          that._lastProgressCallback = Date.now()
        }
        switch (evt) {
          case STATUS.PROGRESS:
            if (this._checkProgress()) {
              triggerProgress()
            }
            break
          case STATUS.ERROR:
            this._error()
            this.abort(true)
            uploader._trigger('fileError', rootFile, this, message, chunk)
            break
          case STATUS.SUCCESS:
            this._updateUploadedChunks(message, chunk)
            if (this.error) {
              return
            }
            clearTimeout(this._progeressId)
            this._progeressId = 0
            var timeDiff = Date.now() - this._lastProgressCallback
            if (timeDiff < uploader.opts.progressCallbacksInterval) {
              this._progeressId = setTimeout(triggerProgress, uploader.opts.progressCallbacksInterval - timeDiff)
            }
            if (this.isComplete()) {
              clearTimeout(this._progeressId)
              triggerProgress(true)
              this.currentSpeed = 0
              this.averageSpeed = 0
              uploader._trigger('fileSuccess', rootFile, this, message, chunk)
              if (rootFile.isComplete()) {
                uploader._trigger('fileComplete', rootFile, this)
              }
            } else if (!this._progeressId) {
              triggerProgress()
            }
            break
          case STATUS.RETRY:
            uploader._trigger('fileRetry', rootFile, this, chunk)
            break
        }
      },
    
      _updateUploadedChunks: function (message, chunk) {
        var checkChunkUploaded = this.uploader.opts.checkChunkUploadedByResponse
        if (checkChunkUploaded) {
          var xhr = chunk.xhr
          utils.each(this.chunks, function (_chunk) {
            if (!_chunk.tested) {
              var uploaded = checkChunkUploaded.call(this, _chunk, message)
              if (_chunk === chunk && !uploaded) {
                // fix the first chunk xhr status
                // treated as success but checkChunkUploaded is false
                // so the current chunk should be uploaded again
                _chunk.xhr = null
              }
              if (uploaded) {
                // first success and other chunks are uploaded
                // then set xhr, so the uploaded chunks
                // will be treated as success too
                _chunk.xhr = xhr
              }
              _chunk.tested = true
            }
          }, this)
          if (!this._firstResponse) {
            this._firstResponse = true
            this.uploader.upload(true)
          } else {
            this.uploader.uploadNextChunk()
          }
        } else {
          this.uploader.uploadNextChunk()
        }
      },
    
      _error: function () {
        this.error = this.allError = true
        var parent = this.parent
        while (parent && parent !== this.uploader) {
          parent._errorFiles.push(this)
          parent.error = true
          if (parent._errorFiles.length === parent.files.length) {
            parent.allError = true
          }
          parent = parent.parent
        }
      },
    
      _resetError: function () {
        this.error = this.allError = false
        var parent = this.parent
        var index = -1
        while (parent && parent !== this.uploader) {
          index = parent._errorFiles.indexOf(this)
          parent._errorFiles.splice(index, 1)
          parent.allError = false
          if (!parent._errorFiles.length) {
            parent.error = false
          }
          parent = parent.parent
        }
      },
    
      isComplete: function () {
        var outstanding = false
        this._eachAccess(function (file) {
          if (!file.isComplete()) {
            outstanding = true
            return false
          }
        }, function () {
          var STATUS = Chunk.STATUS
          utils.each(this.chunks, function (chunk) {
            var status = chunk.status()
            if (status === STATUS.PENDING || status === STATUS.UPLOADING || status === STATUS.READING || chunk.preprocessState === 1 || chunk.readState === 1) {
              outstanding = true
              return false
            }
          })
        })
        return !outstanding
      },
    
      isUploading: function () {
        var uploading = false
        this._eachAccess(function (file) {
          if (file.isUploading()) {
            uploading = true
            return false
          }
        }, function () {
          var uploadingStatus = Chunk.STATUS.UPLOADING
          utils.each(this.chunks, function (chunk) {
            if (chunk.status() === uploadingStatus) {
              uploading = true
              return false
            }
          })
        })
        return uploading
      },
    
      resume: function () {
        this._eachAccess(function (f) {
          f.resume()
        }, function () {
          this.paused = false
          this.aborted = false
          this.uploader.upload()
        })
        this.paused = false
        this.aborted = false
      },
    
      pause: function () {
        this._eachAccess(function (f) {
          f.pause()
        }, function () {
          this.paused = true
          this.abort()
        })
        this.paused = true
      },
    
      cancel: function () {
        this.uploader.removeFile(this)
      },
    
      retry: function (file) {
        var fileRetry = function (file) {
          if (file.error) {
            file.bootstrap()
          }
        }
        if (file) {
          file.bootstrap()
        } else {
          this._eachAccess(fileRetry, function () {
            this.bootstrap()
          })
        }
        this.uploader.upload()
      },
    
      abort: function (reset) {
        if (this.aborted) {
          return
        }
        this.currentSpeed = 0
        this.averageSpeed = 0
        this.aborted = !reset
        var chunks = this.chunks
        if (reset) {
          this.chunks = []
        }
        var uploadingStatus = Chunk.STATUS.UPLOADING
        utils.each(chunks, function (c) {
          if (c.status() === uploadingStatus) {
            c.abort()
            this.uploader.uploadNextChunk()
          }
        }, this)
      },
    
      progress: function () {
        var totalDone = 0
        var totalSize = 0
        var ret = 0
        this._eachAccess(function (file, index) {
          totalDone += file.progress() * file.size
          totalSize += file.size
          if (index === this.files.length - 1) {
            ret = totalSize > 0 ? totalDone / totalSize : this.isComplete() ? 1 : 0
          }
        }, function () {
          if (this.error) {
            ret = 1
            return
          }
          if (this.chunks.length === 1) {
            this._prevProgress = Math.max(this._prevProgress, this.chunks[0].progress())
            ret = this._prevProgress
            return
          }
          // Sum up progress across everything
          var bytesLoaded = 0
          utils.each(this.chunks, function (c) {
            // get chunk progress relative to entire file
            bytesLoaded += c.progress() * (c.endByte - c.startByte)
          })
          var percent = bytesLoaded / this.size
          // We don't want to lose percentages when an upload is paused
          this._prevProgress = Math.max(this._prevProgress, percent > 0.9999 ? 1 : percent)
          ret = this._prevProgress
        })
        return ret
      },
    
      getSize: function () {
        var size = 0
        this._eachAccess(function (file) {
          size += file.size
        }, function () {
          size += this.size
        })
        return size
      },
    
      getFormatSize: function () {
        var size = this.getSize()
        return utils.formatSize(size)
      },
    
      getRoot: function () {
        if (this.isRoot) {
          return this
        }
        var parent = this.parent
        while (parent) {
          if (parent.parent === this.uploader) {
            // find it
            return parent
          }
          parent = parent.parent
        }
        return this
      },
    
      sizeUploaded: function () {
        var size = 0
        this._eachAccess(function (file) {
          size += file.sizeUploaded()
        }, function () {
          utils.each(this.chunks, function (chunk) {
            size += chunk.sizeUploaded()
          })
        })
        return size
      },
    
      timeRemaining: function () {
        var ret = 0
        var sizeDelta = 0
        var averageSpeed = 0
        this._eachAccess(function (file, i) {
          if (!file.paused && !file.error) {
            sizeDelta += file.size - file.sizeUploaded()
            averageSpeed += file.averageSpeed
          }
          if (i === this.files.length - 1) {
            ret = calRet(sizeDelta, averageSpeed)
          }
        }, function () {
          if (this.paused || this.error) {
            ret = 0
            return
          }
          var delta = this.size - this.sizeUploaded()
          ret = calRet(delta, this.averageSpeed)
        })
        return ret
        function calRet (delta, averageSpeed) {
          if (delta && !averageSpeed) {
            return Number.POSITIVE_INFINITY
          }
          if (!delta && !averageSpeed) {
            return 0
          }
          return Math.floor(delta / averageSpeed)
        }
      },
    
      removeFile: function (file) {
        if (file.isFolder) {
          while (file.files.length) {
            var f = file.files[file.files.length - 1]
            this._removeFile(f)
          }
        }
        this._removeFile(file)
      },
    
      _delFilePath: function (file) {
        if (file.path && this.filePaths) {
          delete this.filePaths[file.path]
        }
        utils.each(file.fileList, function (file) {
          this._delFilePath(file)
        }, this)
      },
    
      _removeFile: function (file) {
        if (!file.isFolder) {
          utils.each(this.files, function (f, i) {
            if (f === file) {
              this.files.splice(i, 1)
              return false
            }
          }, this)
          file.abort()
          var parent = file.parent
          var newParent
          while (parent && parent !== this) {
            newParent = parent.parent
            parent._removeFile(file)
            parent = newParent
          }
        }
        file.parent === this && utils.each(this.fileList, function (f, i) {
          if (f === file) {
            this.fileList.splice(i, 1)
            return false
          }
        }, this)
        if (!this.isRoot && this.isFolder && !this.files.length) {
          this.parent._removeFile(this)
          this.uploader._delFilePath(this)
        }
        file.parent = null
      },
    
      getType: function () {
        if (this.isFolder) {
          return 'folder'
        }
        return this.file.type && this.file.type.split('/')[1]
      },
    
      getExtension: function () {
        if (this.isFolder) {
          return ''
        }
        return this.name.substr((~-this.name.lastIndexOf('.') >>> 0) + 2).toLowerCase()
      }
    
    })
    
    module.exports = File
    
    function parsePaths (path) {
      var ret = []
      var paths = path.split('/')
      var len = paths.length
      var i = 1
      paths.splice(len - 1, 1)
      len--
      if (paths.length) {
        while (i <= len) {
          ret.push(paths.slice(0, i++).join('/') + '/')
        }
      }
      return ret
    }
    
    },{"./chunk":1,"./utils":5}],5:[function(_dereq_,module,exports){
    var oproto = Object.prototype
    var aproto = Array.prototype
    var serialize = oproto.toString
    
    var isFunction = function (fn) {
      return serialize.call(fn) === '[object Function]'
    }
    
    var isArray = Array.isArray || /* istanbul ignore next */ function (ary) {
      return serialize.call(ary) === '[object Array]'
    }
    
    var isPlainObject = function (obj) {
      return serialize.call(obj) === '[object Object]' && Object.getPrototypeOf(obj) === oproto
    }
    
    var i = 0
    var utils = {
      uid: function () {
        return ++i
      },
      noop: function () {},
      bind: function (fn, context) {
        return function () {
          return fn.apply(context, arguments)
        }
      },
      preventEvent: function (evt) {
        evt.preventDefault()
      },
      stop: function (evt) {
        evt.preventDefault()
        evt.stopPropagation()
      },
      nextTick: function (fn, context) {
        setTimeout(utils.bind(fn, context), 0)
      },
      toArray: function (ary, start, end) {
        if (start === undefined) start = 0
        if (end === undefined) end = ary.length
        return aproto.slice.call(ary, start, end)
      },
    
      isPlainObject: isPlainObject,
      isFunction: isFunction,
      isArray: isArray,
      isObject: function (obj) {
        return Object(obj) === obj
      },
      isString: function (s) {
        return typeof s === 'string'
      },
      isUndefined: function (a) {
        return typeof a === 'undefined'
      },
      isDefined: function (a) {
        return typeof a !== 'undefined'
      },
    
      each: function (ary, func, context) {
        if (utils.isDefined(ary.length)) {
          for (var i = 0, len = ary.length; i < len; i++) {
            if (func.call(context, ary[i], i, ary) === false) {
              break
            }
          }
        } else {
          for (var k in ary) {
            if (func.call(context, ary[k], k, ary) === false) {
              break
            }
          }
        }
      },
    
      /**
       * If option is a function, evaluate it with given params
       * @param {*} data
       * @param {...} args arguments of a callback
       * @returns {*}
       */
      evalOpts: function (data, args) {
        if (utils.isFunction(data)) {
          // `arguments` is an object, not array, in FF, so:
          args = utils.toArray(arguments)
          data = data.apply(null, args.slice(1))
        }
        return data
      },
    
      extend: function () {
        var options
        var name
        var src
        var copy
        var copyIsArray
        var clone
        var target = arguments[0] || {}
        var i = 1
        var length = arguments.length
        var force = false
    
        // 如果第一个参数为布尔,判定是否深拷贝
        if (typeof target === 'boolean') {
          force = target
          target = arguments[1] || {}
          i++
        }
    
        // 确保接受方为一个复杂的数据类型
        if (typeof target !== 'object' && !isFunction(target)) {
          target = {}
        }
    
        // 如果只有一个参数，那么新成员添加于 extend 所在的对象上
        if (i === length) {
          target = this
          i--
        }
    
        for (; i < length; i++) {
          // 只处理非空参数
          if ((options = arguments[i]) != null) {
            for (name in options) {
              src = target[name]
              copy = options[name]
    
              // 防止环引用
              if (target === copy) {
                continue
              }
              if (force && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
                if (copyIsArray) {
                  copyIsArray = false
                  clone = src && isArray(src) ? src : []
                } else {
                  clone = src && isPlainObject(src) ? src : {}
                }
                target[name] = utils.extend(force, clone, copy)
              } else if (copy !== undefined) {
                target[name] = copy
              }
            }
          }
        }
        return target
      },
    
      formatSize: function (size) {
        if (size < 1024) {
          return size.toFixed(0) + ' bytes'
        } else if (size < 1024 * 1024) {
          return (size / 1024.0).toFixed(0) + ' KB'
        } else if (size < 1024 * 1024 * 1024) {
          return (size / 1024.0 / 1024.0).toFixed(1) + ' MB'
        } else {
          return (size / 1024.0 / 1024.0 / 1024.0).toFixed(1) + ' GB'
        }
      },
    
      defineNonEnumerable: function (target, key, value) {
        Object.defineProperty(target, key, {
          enumerable: false,
          configurable: true,
          writable: true,
          value: value
        })
      }
    }
    
    module.exports = utils
    
    },{}]},{},[3])
    (3)
    }));
